////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNMemory/inc/WNMemory.h"
#include "WNMath/inc/WNSphere.h"
#include "WNMath/inc/WNBasic.h"
#include "WNMath/inc/WNExtended.h"
#include "WNMath/inc/WNVector3.h"
#include "WNMath/inc/WNBox.h"
#include "WNMath/inc/WNBounds3.h"

using namespace WNMath;
using namespace WNMemory;

// Constructors ////////////////////////////////////////////////////////////////////////////////

WNSphere::WNSphere() :
    location(0.0f, 0.0f, 0.0f),
    radius(0.0f) {
}

WNSphere::WNSphere(const WN_FLOAT32* _f) :
    location(_f[0], _f[1], _f[2]),
    radius(_f[3]) {
}

WNSphere::WNSphere(const WNPoint3& _location, WN_FLOAT32 _radius) :
    location(_location),
    radius(_radius) {
}

WNSphere::WNSphere(WN_FLOAT32 _x, WN_FLOAT32 _y, WN_FLOAT32 _z, WN_FLOAT32 _radius) :
    location(_x, _y, _z),
    radius(_radius) {
}

// Operators ///////////////////////////////////////////////////////////////////////////////////

WN_BOOL WNSphere::operator == (const WNSphere& _sphere) const {
    return(0 == WNMemCmp(this, &_sphere, sizeof(WNSphere)));
}

WN_BOOL WNSphere::operator != (const WNSphere& _sphere) const {
    return(0 != WNMemCmp(this, &_sphere, sizeof(WNSphere)));
}

// Members /////////////////////////////////////////////////////////////////////////////////////

WN_VOID WNSphere::Zero() {
    location.Zero();
    radius = 0.0f;
}

WN_VOID WNSphere::Set(const WN_FLOAT32* _f) {
    location.x = _f[0];
    location.y = _f[1];
    location.z = _f[2];
    radius = _f[3];
}

WN_VOID WNSphere::Set(const WNPoint3& _location, WN_FLOAT32 _radius) {
    location = _location;
    radius = _radius;
}

WN_VOID WNSphere::Set(WN_FLOAT32 _x, WN_FLOAT32 _y, WN_FLOAT32 _z, WN_FLOAT32 _radius) {
    location.x = _x;
    location.y = _y;
    location.z = _z;
    radius = _radius;
}

WN_BOOL WNSphere::IsZero() const {
    return(location.IsZero() && radius == 0.0f);
}

WN_BOOL WNSphere::IsInsideOut() const {
    return(radius < 0.0f);
}

WN_FLOAT32 WNSphere::Volume() const {
    return((4.0f / 3.0f) * static_cast<WN_FLOAT32>(WN_PI) * radius * radius * radius);
}

WN_FLOAT32 WNSphere::SurfaceArea() const {
    return(4.0f * static_cast<WN_FLOAT32>(WN_PI) * radius * radius);
}

WN_VOID WNSphere::Expand(WN_FLOAT32 _amount) {
    radius += _amount;
}

WN_BOOL WNSphere::Expand(const WNPoint3& _point, WN_BOOL _anchor) {
    if (radius < 0.0f) {
        if (!_anchor) {
            location = _point;
            radius = 0.0f;
        } else {
            radius = WNDistance(location, _point);
        }

        return(WN_TRUE);
    } else {
        const WN_FLOAT32 distanceSquared = WNDistanceSquared(location, _point);

        if (distanceSquared > radius * radius) {
            const WN_FLOAT32 distance = WNSqrt(distanceSquared);

            if (!_anchor) {
                location += (_point - location) * 0.5f * (1.0f - radius / distance);
                radius += 0.5f * (distance - radius);
            } else {
                radius = distance;
            }

            return(WN_TRUE);
        }
    }

    return(WN_FALSE);
}

WN_BOOL WNSphere::Expand(const WNBox& _box, WN_BOOL _anchor) {
    WNPoint3 points[8];

    _box.GetPoints(points);

    return(Expand(points[0], _anchor) || Expand(points[1], _anchor) ||
           Expand(points[2], _anchor) || Expand(points[3], _anchor) ||
           Expand(points[4], _anchor) || Expand(points[5], _anchor) ||
           Expand(points[6], _anchor) || Expand(points[7], _anchor));
}

WN_BOOL WNSphere::Expand(const WNBounds3& _bounds, WN_BOOL _anchor) {
    WNPoint3 points[8];

    _bounds.GetPoints(points);

    return(Expand(points[0], _anchor) || Expand(points[1], _anchor) ||
           Expand(points[2], _anchor) || Expand(points[3], _anchor) ||
           Expand(points[4], _anchor) || Expand(points[5], _anchor) ||
           Expand(points[6], _anchor) || Expand(points[7], _anchor));
}

WN_BOOL WNSphere::Expand(const WNSphere& _sphere, WN_BOOL _anchor) {
    if (radius < 0.0f) {
        if (!_anchor) {
            location = _sphere.location;
            radius = _sphere.radius;
        } else {
            radius = WNDistance(location, _sphere.location) + _sphere.radius;
        }

        return(WN_TRUE);
    } else {
        const WN_FLOAT32 distanceSquared = WNDistanceSquared(location, _sphere.location);

        if (distanceSquared > (radius + _sphere.radius) * (radius + _sphere.radius)) {
            const WN_FLOAT32 distance = WNSqrt(distanceSquared);

            if (!_anchor) {
                location += (_sphere.location - location) * 0.5f *
                            (1.0f - radius / (distance + _sphere.radius));
                radius += 0.5f * ((distance + _sphere.radius) - radius);
            } else {
                radius = distance;
            }

            return(WN_TRUE);
        }
    }

    return(WN_FALSE);
}

WN_VOID WNSphere::Translate(const WNVector3& _vector) {
    location += _vector;
}

WNSphere WNSphere::GetExpanded(WN_FLOAT32 _amount) const {
    WNSphere sphere = *this;

    return(sphere.Expand(_amount), sphere);
}

WNSphere WNSphere::GetExpanded(const WNPoint3& _point, WN_BOOL _anchor) const {
    WNSphere sphere = *this;

    return(sphere.Expand(_point, _anchor), sphere);
}

WNSphere WNSphere::GetExpanded(const WNBox& _box, WN_BOOL _anchor) const {
    WNSphere sphere = *this;

    return(sphere.Expand(_box, _anchor), sphere);
}

WNSphere WNSphere::GetExpanded(const WNBounds3& _bounds, WN_BOOL _anchor) const {
    WNSphere sphere = *this;

    return(sphere.Expand(_bounds, _anchor), sphere);
}

WNSphere WNSphere::GetExpanded(const WNSphere& _sphere, WN_BOOL _anchor) const {
    WNSphere sphere = *this;

    return(sphere.Expand(_sphere, _anchor), sphere);
}

WNSphere WNSphere::GetTranslated(const WNVector3& _vector) const {
    WNSphere sphere = *this;

    return(sphere.Translate(_vector), sphere);
}